// shared_mutex standard header
#pragma once
#ifndef _SHARED_MUTEX_
#define _SHARED_MUTEX_
#ifndef RC_INVOKED

#ifdef _M_CEE_PURE
#error <shared_mutex> is not supported when compiling with /clr:pure.
#endif // _M_CEE_PURE

#include <mutex>
#include <thr/xthreads.h>
#ifndef _M_CEE
#include <condition_variable>
#endif // _M_CEE

#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
_STL_DISABLE_CLANG_WARNINGS
#pragma push_macro("new")
#undef new


_STD_BEGIN
#ifdef __cpp_lib_shared_mutex
// CLASS shared_mutex
class shared_mutex { // class for mutual exclusion shared across threads
public:
    using native_handle_type = _Smtx_t*;

    shared_mutex() noexcept : _Myhandle(nullptr) { // default construct
    }

    ~shared_mutex() noexcept { // destroy the object
    }

    void lock() noexcept { // lock exclusive
        _Smtx_lock_exclusive(&_Myhandle);
    }

    _NODISCARD bool try_lock() noexcept { // try to lock exclusive
        return _Smtx_try_lock_exclusive(&_Myhandle) != 0;
    }

    void unlock() noexcept { // unlock exclusive
        _Smtx_unlock_exclusive(&_Myhandle);
    }

    void lock_shared() noexcept { // lock non-exclusive
        _Smtx_lock_shared(&_Myhandle);
    }

    _NODISCARD bool try_lock_shared() noexcept { // try to lock non-exclusive
        return _Smtx_try_lock_shared(&_Myhandle) != 0;
    }

    void unlock_shared() noexcept { // unlock non-exclusive
        _Smtx_unlock_shared(&_Myhandle);
    }

    _NODISCARD native_handle_type native_handle() noexcept { // get native handle
        return &_Myhandle;
    }

    shared_mutex(const shared_mutex&) = delete;
    shared_mutex& operator=(const shared_mutex&) = delete;

private:
    _Smtx_t _Myhandle;
};
#endif // __cpp_lib_shared_mutex

// shared_timed_mutex is not supported under /clr
#ifndef _M_CEE
// CLASS shared_timed_mutex
class shared_timed_mutex { // class for mutual exclusion shared across threads
    using _Read_cnt_t = unsigned int;

    static constexpr _Read_cnt_t _Max_readers = _Read_cnt_t(-1);

public:
    shared_timed_mutex() noexcept
        : _Mymtx(), _Read_queue(), _Write_queue(), _Readers(0), _Writing(false) { // default construct
    }

    ~shared_timed_mutex() noexcept { // destroy the object
    }

    void lock() { // lock exclusive
        unique_lock<mutex> _Lock(_Mymtx);
        while (_Writing) {
            _Write_queue.wait(_Lock);
        }

        _Writing = true;
        while (0 < _Readers) {
            _Read_queue.wait(_Lock); // wait for writing, no readers
        }
    }

    _NODISCARD bool try_lock() { // try to lock exclusive
        lock_guard<mutex> _Lock(_Mymtx);
        if (_Writing || 0 < _Readers) {
            return false;
        } else { // set writing, no readers
            _Writing = true;
            return true;
        }
    }

    template <class _Rep, class _Period>
    _NODISCARD bool try_lock_for(const chrono::duration<_Rep, _Period>& _Rel_time) { // try to lock for duration
        return try_lock_until(chrono::steady_clock::now() + _Rel_time);
    }

    template <class _Clock, class _Duration>
    _NODISCARD bool try_lock_until(
        const chrono::time_point<_Clock, _Duration>& _Abs_time) { // try to lock until time point
        auto _Not_writing  = [this] { return !_Writing; };
        auto _Zero_readers = [this] { return _Readers == 0; };
        unique_lock<mutex> _Lock(_Mymtx);

        if (!_Write_queue.wait_until(_Lock, _Abs_time, _Not_writing)) {
            return false;
        }

        _Writing = true;

        if (!_Read_queue.wait_until(_Lock, _Abs_time, _Zero_readers)) { // timeout, leave writing state
            _Writing = false;
            _Lock.unlock(); // unlock before notifying, for efficiency
            _Write_queue.notify_all();
            return false;
        }

        return true;
    }

    void unlock() { // unlock exclusive
        { // unlock before notifying, for efficiency
            lock_guard<mutex> _Lock(_Mymtx);

            _Writing = false;
        }

        _Write_queue.notify_all();
    }

    void lock_shared() { // lock non-exclusive
        unique_lock<mutex> _Lock(_Mymtx);
        while (_Writing || _Readers == _Max_readers) {
            _Write_queue.wait(_Lock);
        }

        ++_Readers;
    }

    _NODISCARD bool try_lock_shared() { // try to lock non-exclusive
        lock_guard<mutex> _Lock(_Mymtx);
        if (_Writing || _Readers == _Max_readers) {
            return false;
        } else { // count another reader
            ++_Readers;
            return true;
        }
    }

    template <class _Rep, class _Period>
    _NODISCARD bool try_lock_shared_for(
        const chrono::duration<_Rep, _Period>& _Rel_time) { // try to lock non-exclusive for relative time
        return try_lock_shared_until(_Rel_time + chrono::steady_clock::now());
    }

    template <class _Time>
    bool _Try_lock_shared_until(_Time _Abs_time) { // try to lock non-exclusive until absolute time
        const auto _Can_acquire = [this] { return !_Writing && _Readers < _Max_readers; };

        unique_lock<mutex> _Lock(_Mymtx);

        if (!_Write_queue.wait_until(_Lock, _Abs_time, _Can_acquire)) {
            return false;
        }

        ++_Readers;
        return true;
    }

    template <class _Clock, class _Duration>
    _NODISCARD bool try_lock_shared_until(
        const chrono::time_point<_Clock, _Duration>& _Abs_time) { // try to lock non-exclusive until absolute time
        return _Try_lock_shared_until(_Abs_time);
    }

    _NODISCARD bool try_lock_shared_until(const xtime* _Abs_time) { // try to lock non-exclusive until absolute time
        return _Try_lock_shared_until(_Abs_time);
    }

    void unlock_shared() { // unlock non-exclusive
        _Read_cnt_t _Local_readers;
        bool _Local_writing;

        { // unlock before notifying, for efficiency
            lock_guard<mutex> _Lock(_Mymtx);
            --_Readers;
            _Local_readers = _Readers;
            _Local_writing = _Writing;
        }

        if (_Local_writing && _Local_readers == 0) {
            _Read_queue.notify_one();
        } else if (!_Local_writing && _Local_readers == _Max_readers - 1) {
            _Write_queue.notify_all();
        }
    }

    shared_timed_mutex(const shared_timed_mutex&) = delete;
    shared_timed_mutex& operator=(const shared_timed_mutex&) = delete;

private:
    mutex _Mymtx;
    condition_variable _Read_queue, _Write_queue;
    _Read_cnt_t _Readers;
    bool _Writing;
};
#endif // _M_CEE

// CLASS TEMPLATE shared_lock
template <class _Mutex>
class shared_lock { // shareable lock
public:
    using mutex_type = _Mutex;

    shared_lock() noexcept : _Pmtx(nullptr), _Owns(false) { // default construct
    }

    explicit shared_lock(mutex_type& _Mtx)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(true) { // construct with mutex and lock shared
        _Mtx.lock_shared();
    }

    shared_lock(mutex_type& _Mtx, defer_lock_t) noexcept
        : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // construct with unlocked mutex
    }

    shared_lock(mutex_type& _Mtx, try_to_lock_t)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(_Mtx.try_lock_shared()) { // construct with mutex and try to lock shared
    }

    shared_lock(mutex_type& _Mtx, adopt_lock_t)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(true) { // construct with mutex and adopt ownership
    }

    template <class _Rep, class _Period>
    shared_lock(mutex_type& _Mtx, const chrono::duration<_Rep, _Period>& _Rel_time)
        : _Pmtx(_STD addressof(_Mtx)),
          _Owns(_Mtx.try_lock_shared_for(_Rel_time)) { // construct with mutex and try to lock for relative time
    }

    template <class _Clock, class _Duration>
    shared_lock(mutex_type& _Mtx, const chrono::time_point<_Clock, _Duration>& _Abs_time)
        : _Pmtx(_STD addressof(_Mtx)),
          _Owns(_Mtx.try_lock_shared_until(_Abs_time)) { // construct with mutex and try to lock until absolute time
    }

    ~shared_lock() noexcept { // destroy the lock
        if (_Owns) {
            _Pmtx->unlock_shared();
        }
    }

    shared_lock(shared_lock&& _Other) noexcept
        : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns) { // construct by moving _Other
        _Other._Pmtx = nullptr;
        _Other._Owns = false;
    }

    shared_lock& operator=(shared_lock&& _Right) noexcept { // copy by moving _Right
        if (_Owns) {
            _Pmtx->unlock_shared();
        }

        _Pmtx            = _Right._Pmtx;
        _Owns            = _Right._Owns;
        _Right._Pmtx     = nullptr;
        _Right._Owns     = false;
        return *this;
    }

    shared_lock(const shared_lock&) = delete;
    shared_lock& operator=(const shared_lock&) = delete;

    void lock() { // lock the mutex
        _Validate();
        _Pmtx->lock_shared();
        _Owns = true;
    }

    _NODISCARD bool try_lock() { // try to lock the mutex
        _Validate();
        _Owns = _Pmtx->try_lock_shared();
        return _Owns;
    }

    template <class _Rep, class _Period>
    _NODISCARD bool try_lock_for(
        const chrono::duration<_Rep, _Period>& _Rel_time) { // try to lock the mutex for _Rel_time
        _Validate();
        _Owns = _Pmtx->try_lock_shared_for(_Rel_time);
        return _Owns;
    }

    template <class _Clock, class _Duration>
    _NODISCARD bool try_lock_until(
        const chrono::time_point<_Clock, _Duration>& _Abs_time) { // try to lock the mutex until _Abs_time
        _Validate();
        _Owns = _Pmtx->try_lock_shared_until(_Abs_time);
        return _Owns;
    }

    void unlock() { // try to unlock the mutex
        if (!_Pmtx || !_Owns) {
            _THROW(system_error(_STD make_error_code(errc::operation_not_permitted)));
        }

        _Pmtx->unlock_shared();
        _Owns = false;
    }

    // MUTATE
    void swap(shared_lock& _Right) noexcept { // swap with _Right
        _STD swap(_Pmtx, _Right._Pmtx);
        _STD swap(_Owns, _Right._Owns);
    }

    mutex_type* release() noexcept { // release the mutex
        _Mutex* _Res = _Pmtx;
        _Pmtx        = nullptr;
        _Owns        = false;
        return _Res;
    }

    // OBSERVE
    _NODISCARD bool owns_lock() const noexcept { // return true if this object owns the lock
        return _Owns;
    }

    explicit operator bool() const noexcept { // return true if this object owns the lock
        return _Owns;
    }

    _NODISCARD mutex_type* mutex() const noexcept { // return pointer to managed mutex
        return _Pmtx;
    }

private:
    _Mutex* _Pmtx;
    bool _Owns;

    void _Validate() const { // check if the mutex can be locked
        if (!_Pmtx) {
            _THROW(system_error(_STD make_error_code(errc::operation_not_permitted)));
        }

        if (_Owns) {
            _THROW(system_error(_STD make_error_code(errc::resource_deadlock_would_occur)));
        }
    }
};

// SWAP
template <class _Mutex>
void swap(shared_lock<_Mutex>& _Left,
    shared_lock<_Mutex>& _Right) noexcept { // swap two shared locks
    _Left.swap(_Right);
}
_STD_END
#pragma pop_macro("new")
_STL_RESTORE_CLANG_WARNINGS
#pragma warning(pop)
#pragma pack(pop)
#endif // RC_INVOKED
#endif // _SHARED_MUTEX_

/*
 * Copyright (c) by P.J. Plauger. All rights reserved.
 * Consult your license regarding permissions and restrictions.
V6.50:0009 */
